/**
 * WTF Engine
 * 
 * License... etc.
 **
 * Fonts
 * 
 * Features:
 * 	- A Font Class, derived from TextureImage
 *  - writing strings as rendered texture mapped quads.
 ** 
 * Author: Sean Micklethwaite
 */


#include "gui_font.h"
#include "xml.h"
#include <string>

#define NDEBUG

using namespace WtfGui;
using namespace WtfEngine;

#define FONTCHAR_HASH(c)	((c - (c > 32 ? 32 : 0)) % HASHTBL_SIZE)

#define WRAP_SLOP		0.1
#define BASE_HEIGHT		48

Font::Font(const StringPool::Entry& sName, float fHeight, const fVector4D& vColour)
	: WtfEngine::TextureImage(sName), mfHeight(fHeight), mvColour(vColour)
{
	String			sPath(sName);
	XmlFile			manifest;
	int				c, w, h, u, v, pre, post, y;
	
	for(c = 0; c < HASHTBL_SIZE; c++)
		this->mzFontCharTbl[c] = NULL;
	
	// Load the font image
	this->WtfEngine::TextureImage::Read(sPath + ".png", true);
	
	// Load the manifest
	manifest.Load(sPath + ".xml");
	if(manifest.GetName() != "FontDetails")
		throw std::logic_error(String("Parse error in manifest: ") + sPath);
	
	// Parse it
	while(manifest.NextNode())
	{
		if(manifest.GetName() == "character"){
			c = manifest.GetAttribute<int>("code");
			post = manifest.GetAttribute<int>("postshift");
			
			if(manifest.GetAttribute("u") != ""){
				w = manifest.GetAttribute<int>("u2");
				h = manifest.GetAttribute<int>("v2");
				u = manifest.GetAttribute<int>("u");
				v = manifest.GetAttribute<int>("v");

				pre = manifest.GetAttribute<int>("preshift");;
				y = manifest.GetAttribute<int>("yadjust");

			}else{
				// Whitespace char
				w = h = u = v = 255;
				pre = 0; y = 0;
			};

			this->InsertChar(new FontChar(c, w - u, h - v, u, v, pre, post, y));
		};
	};
};

WtfEngine::IDataObject * Font::Load(const StringPool::Entry& path)
{
	Font *	pFont = new Font(path);
	return pFont;
};

/**
 * Draws the string as individual texture mapped quads.
 **/
void Font::DrawText(const WtfEngine::String& s, const iVector2D& vBounds, tTextAlignment align)
{
	FontChar*	pChar;
	int			i;
	float		h = mfHeight, x = 0, y = -h, scalex, scaley, w;

	const tChar *sText = s.str();

	Bind();
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	w = GetLineWidth(sText);
	scalex = ((float)h * ((float)vBounds[0] / maxf((float)vBounds[0], w))) / BASE_HEIGHT;
	scaley = ((float)h)/BASE_HEIGHT;

	for(i = 0; sText[i]; i++){
		if((sText[i] == '\r') || (sText[i] == '\n')){
			x = 0; y -= h;
			w = GetLineWidth(sText + i + 1);
			scalex = ((float)h * ((float)vBounds[0] / maxf((float)vBounds[0], w))) / BASE_HEIGHT;
			continue;
		};

		pChar = LookupChar(sText[i]);
		if(!pChar) {
			pChar = LookupChar('?');

			if(!pChar)
				throw "Invalid font - '?' character not found!";
		};

		// TODO: Word wrap - this is only char wrap
		if((x + (pChar->Post * scalex)) - WRAP_SLOP > vBounds[0]){
			x = 0; y -= mfHeight;
		};

		if(y < (-vBounds[1]))
			break;
		
		glBegin(GL_QUADS);
			glColor4fv(mvColour.toArray());
			glTexCoord2f(pChar->U, pChar->V);
			glVertex2f(x + (pChar->Pre * scalex), y + (pChar->Y * scaley));
			glTexCoord2f(pChar->U, pChar->V + pChar->Height);
			glVertex2f(x + (pChar->Pre * scalex), y + ((pChar->Y - pChar->Height) * scaley));
			glTexCoord2f(pChar->U + pChar->Width, pChar->V + pChar->Height);
			glVertex2f(x + (pChar->Width * scalex) + (pChar->Pre * scalex), y + ((pChar->Y - pChar->Height) * scaley));
			glTexCoord2f(pChar->U + pChar->Width, pChar->V);
			glVertex2f(x + (pChar->Width * scalex) + (pChar->Pre * scalex), y + (pChar->Y * scaley));
		glEnd();

		x += ((pChar->Post) * scalex);
	};

	Release();
};

float Font::GetLineWidth(const tChar * s) const {
	FontChar*	pChar;
	float x = 0, scale = (float)mfHeight / BASE_HEIGHT;
	while(*s && *s != '\n' && *s != '\r') {
		pChar = LookupChar(*s++);
		if(!pChar) {
			pChar = LookupChar('?');
			if(!pChar)
				throw "Invalid font - '?' character not found!";
		};

		x += ((pChar->Post) * scale);
	};
	return x;
};

iVector2D Font::GetSize(const String& s, float wrapWidth)
{
	const tChar *sText = s.str();
	FontChar*	pChar;
	int			i;
	float		h = mfHeight, x = 0, m = 0, y = -h, scale = (float)h / BASE_HEIGHT;
	
	Bind();

	for(i = 0; sText[i]; i++){
		if((sText[i] == '\r') || (sText[i] == '\n')){
			m = maxf(x, m); x = 0; y -= h;
			continue;
		};

		pChar = LookupChar(sText[i]);
		if(!pChar)
			pChar = LookupChar('?');

		if(!pChar)
			throw "Invalid font - '?' character not found!";

		// TODO: Word wrap

		x += ((pChar->Post) * scale);
	};
	
	return iVector2D(ceil(maxf(x, m)), ceil(-y));
};


/**
 * Quick and dirty hash table for storing the font character data
 **/
void Font::InsertChar(WtfGui::Font::FontChar *pChar)
{
	int h =	FONTCHAR_HASH(pChar->CharCode);
	pChar->Next = mzFontCharTbl[h];
	mzFontCharTbl[h] = pChar;
};

Font::FontChar* Font::LookupChar(unsigned charCode) const
{
	FontChar*	pChar;
	
	for(pChar = mzFontCharTbl[FONTCHAR_HASH(charCode)];
		pChar != NULL; pChar = pChar->Next)
	{
		if(pChar->CharCode == charCode) break;
#ifndef NDEBUG
		char cs[4];
		cs[0] = (char)charCode; cs[2] = (char)pChar->CharCode; cs[1] = cs[3] = NULL;
		Log(LOG_DEBUG, String("Font hash table miss: char ") + cs
				+ " val " + (cs + 2));
#endif
	};

	return pChar;
};

